﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace Netcached
{
    class SocketWrapper : IDisposable
    {
        public string ServerAddress { get; private set; }
        public bool IsWorking { get; set; }

        private bool markedForDisposal;
        private Stream stream;
        private Socket socket;
        private SocketPool parentPool;
        private bool disposed;

        Guid identifier;
        public Guid Identifier
        {
            get
            {
                if (identifier == Guid.Empty)
                {
                    identifier = Guid.NewGuid();
                }

                return identifier;
            }
        }

        public bool IsAlive
        {
            get
            {
                return !disposed && socket != null && socket.Connected && socket.Poll(100, SelectMode.SelectWrite);
            }
        }

        public SocketWrapper(string serverAddress, int port, int sendReceiveTimeout, SocketPool parentPool)
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            
            socket.NoDelay = true;
            socket.ReceiveTimeout = sendReceiveTimeout;
            socket.SendTimeout = sendReceiveTimeout;
            this.parentPool = parentPool;
            this.ServerAddress = serverAddress;

            socket.Connect(serverAddress, port);
            //var result = socket.BeginConnect(serverAddress, port, null, null);
            //bool success = result.AsyncWaitHandle.WaitOne(connectTimeout, false);
            
            //if (!success)
            //{
            //    var ex = new SocketException();
            //    throw ex;
            //}

            //socket.EndConnect(result);

            stream = new BufferedStream(new NetworkStream(socket));
        }

        #region Read/Write functionality

        public void Send(string content)
        {
            var buffer = Encoding.UTF8.GetBytes(content);
            Send(buffer);
        }

        public void Send(byte[] content)
        {
            stream.Write(content, 0, content.Length);
            stream.Flush();
        }

        public string ReadTextLine()
        {
            string result = null;
            int readByte = 0;
            bool foundReturn = false;
            MemoryStream readStream = new MemoryStream();

            while ((readByte = stream.ReadByte()) != -1)
            {
                if ((char)readByte == '\n' && foundReturn)
                {
                    break;
                }
                else if ((char)readByte == '\r')
                {
                    foundReturn = true;
                }
                else
                {
                    if (foundReturn)
                    {
                        readStream.WriteByte(13); // write carriage return from previous read
                        foundReturn = false;
                    }
                    readStream.WriteByte((byte)readByte);
                }
            }

            result = Encoding.UTF8.GetString(readStream.GetBuffer());
            readStream.Close();

            ParseErrors(result);

            return result;
        }

        private void ParseErrors(string line)
        {
            if (string.IsNullOrEmpty(line))
            {
                throw new MemcachedException("Received empty response from the server");
            }

            if (line.StartsWith("ERROR"))
            {
                throw new MemcachedException("Client sent a nonexistent command name.");
            }

            if (line.StartsWith("CLIENT_ERROR") || line.StartsWith("SERVER_ERROR"))
            {
                throw new MemcachedException(line);
            }
        }

        public byte[] ReadData(int expectedBytes)
        {
            var byteArray = new byte[expectedBytes];
            stream.Read(byteArray, 0, expectedBytes);
            
            // shave off newline characters
            stream.ReadByte();
            stream.ReadByte();

            return byteArray;
        }

        #endregion

        public void ReturnToPool()
        {
            IsWorking = false;

            if (markedForDisposal)
            {
                Dispose();
            }
            else
            {
                parentPool.ReturnSocketToPool(this);
            }
        }

        public void Dispose()
        {
            if (!IsWorking)
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
            else
            {
                markedForDisposal = true;
            }
        }

        protected void Dispose(bool disposing)
        {
            System.Diagnostics.Debug.Write(string.Format("Disposing socket: {0} \r\n", identifier));

            if (!disposed)
            {
                if (disposing)
                {
                    stream.Close();
                    socket.Close();
                }

                disposed = true;
            }
        }
    }
}
